Skip to main content
Glama

Windows Diagnostics MCP Server

by jackalterman
Adding New Tools.md8.76 kB
# Guide to Adding New Tools to `windows-diagnostic-mcp-server` This guide reflects how tools work in this repository today. It covers creating a PowerShell script, defining its JSON output, adding TypeScript types, writing a tool wrapper, and registering the tool in the MCP server. --- ## 1) PowerShell script (`src/powershell_scripts/`) Scripts do the actual Windows work and must return a single JSON object on stdout when called by the server. - **Location**: place new scripts in `src/powershell_scripts/`. - **Naming**: use `snake_case` (e.g., `my_new_tool.ps1`). - **Parameters**: - Define a `param(...)` block. Use `[switch]` for booleans so passing `-Flag` is enough. - If you need arrays, prefer accepting them as comma-separated strings and split them in-script. Example pattern used in `event_viewer.ps1`: - Param: `[string]$EventIDs = ""` - Convert: `$EventIDs = $EventIDs ? ($EventIDs.Split(',') | % { [int]$_.Trim() }) : @()` - Include a `[switch]$JsonOutput` parameter if you want the script to support both JSON and human console output. The TypeScript wrappers pass `JsonOutput: true`. - **Output contract**: - Build a hashtable like `$Results = @{ ... }` and populate strongly shaped properties (arrays, nested objects). - End with JSON only on stdout. Typical patterns in this repo: - Always JSON: `$Results | ConvertTo-Json -Depth 10` - Or gated by switch: `if ($JsonOutput) { $Results | ConvertTo-Json -Depth 10 } else { <human-readable output> }` - Do not write other content to stdout when emitting JSON. If you want debug output, prefer `Write-Error` or add fields to `$Results` like `Errors`, `Warnings`, or `DebugInfo`. - **Error handling**: wrap potentially failing operations with `try/catch` and add messages to `$Results.Errors` rather than throwing. - **Doc comments**: include `.SYNOPSIS` / `.DESCRIPTION` at the top. Minimal template: ```powershell param( [switch]$JsonOutput, [string]$Items = "" # "a,b,c"; split inside script if needed ) $Results = @{ Items = @() Errors = @() } try { $arr = if ($Items -and $Items.Trim() -ne "") { $Items.Split(',') | % { $_.Trim() } } else { @() } foreach ($i in $arr) { $Results.Items += @{ Name = $i } } } catch { $Results.Errors += $_.Exception.Message } if ($JsonOutput) { $Results | ConvertTo-Json -Depth 10 } else { # optional console output for direct use $Results } ``` --- ## 2) Shared TypeScript types (`src/types.ts`) Add interfaces that match your script’s JSON output and, optionally, an interface for the tool params. - **Location**: `src/types.ts`. - **Outputs**: create interfaces with exact property names/types you emit from PowerShell. See existing types like `DiagnosticResults`, `RegistryDiagnosticResults`, `HardwareMonitorOutput` for patterns. - **Params**: define an interface if helpful (e.g., `export interface MyToolParams { foo?: string; }`). Arrays in wrappers are usually `string[]` then converted to comma-separated strings for PowerShell. Example: ```ts export interface MyToolItem { Name: string } export interface MyToolOutput { Items: MyToolItem[]; Errors: string[] } export interface MyToolParams { Items?: string[] } ``` --- ## 3) TypeScript tool wrapper (`src/tools/`) Wrappers read the `.ps1` content and invoke it via the shared runner `runPowerShellScript`. They return MCP-formatted content. - **Location**: `src/tools/`. - **Imports**: - `runPowerShellScript` from `../utils.js` (note the `.js` extension because the build emits JS). - `* as fs`, `* as path`, and `fileURLToPath` to resolve the script path. - Types from `../types.js` (again, `.js` at runtime). - **Script loading**: - Use `const __dirname = path.dirname(fileURLToPath(import.meta.url));` - Resolve and `fs.readFileSync` the `.ps1` file to a string (current wrappers pass script content, not a file path). - **Parameter mapping**: - Booleans: pass `true` to include a PowerShell switch; omit if `false`. - Arrays: pass a string array; the runner will join with commas into a single param (`'a,b,c'`). Ensure your script splits it. - Always include `JsonOutput: true` if your script supports it. - **Return format**: return MCP response with `content: [{ type: 'text', text: markdownOrText }]`. Minimal template: ```ts import { runPowerShellScript } from '../utils.js' import * as AllTypes from '../types.js' import * as fs from 'fs' import * as path from 'path' import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const SCRIPT_PATH = path.resolve(__dirname, '../powershell_scripts/my_new_tool.ps1') const SCRIPT_CONTENT = fs.readFileSync(SCRIPT_PATH, 'utf-8') export async function myNewTool(args: AllTypes.MyToolParams = {}) { const result = await runPowerShellScript( SCRIPT_CONTENT, { JsonOutput: true, Items: (args.Items ?? []).map(String), // becomes 'a,b,c' } ) as AllTypes.MyToolOutput return { content: [{ type: 'text', text: `Found ${result.Items.length} item(s)` }], } } ``` --- ## 4) Register the tool in the MCP server (`src/index.ts`) Add the tool to the list and implement the handler case. 1. Import your wrapper function near the top: ```ts import * as myTool from './tools/my_new_tool.js' ``` 2. Add a new entry to the `tools` array in the ListTools handler with its `name`, `description`, and `inputSchema` (JSON Schema): ```ts { name: 'my_new_tool', description: 'Describe what it does', inputSchema: { type: 'object', properties: { Items: { type: 'array', items: { type: 'string' } }, }, }, } ``` 3. Add a case in the CallTool handler switch to route calls: ```ts case 'my_new_tool': return await myTool.myNewTool(args as AllTypes.MyToolParams) ``` Use existing registrations (e.g., `hardware_monitor`, `event_viewer`, registry tools) as references for style. ## 5) Conventions used by the runner (`src/utils.ts`) `runPowerShellScript(scriptContent, params, options?)` executes PowerShell and parses JSON from stdout. - **Booleans**: `true` becomes `-Flag` (omitted if `false`). - **Arrays**: joined into a single comma-separated string: `-Names 'a,b,c'`. - **Strings/numbers**: quoted and escaped: `-Param 'value'`. - **Script content vs file**: current tools pass content; file execution is supported with `{ useScriptFile: true }`. - The process fails if PowerShell exits non-zero or stdout is not valid JSON. --- ## 6) Practical examples in this repo - **Event Viewer** (`event_viewer.ps1` + `src/tools/event_viewer.ts`): - Arrays passed as comma-separated strings (`EventIDs`, `Sources`, `LogNames`). - Wrapper converts `number[]` to strings and sets `JsonOutput: true`. - Returns a rich markdown summary. - **Diagnostics** (`diagnostic.ps1` + `src/tools/diagnostics.ts`): - Uses `[switch]$Detailed` and `[switch]$JsonOutput`. - Wrapper maps booleans and `daysBack` to `DaysBack`. - **Registry** (`windows_registry.ps1` + `src/tools/registry.ts`): - Script supports many switches (e.g., `-ScanStartup`, `-FindOrphaned`). - Wrapper always passes `JsonOutput: true`. - **Apps & Processes** (`apps_and_processes.ps1` + `src/tools/apps_and_processes.ts`): - Multiple operations in one script; wrapper selects behavior by passing the relevant params. - **Hardware Monitor** (`hardware_monitor.ps1` + `src/tools/hardware_monitor.ts`): - All boolean checks default to true if not provided; wrapper can override. --- ## 7) Checklist for adding a new tool - PowerShell script created in `src/powershell_scripts/` with: - `param(...)` using switches for booleans - `$Results` object, `Errors` array - Final `ConvertTo-Json -Depth 10` on stdout (or gated by `$JsonOutput`) - No extra stdout when producing JSON - Types added to `src/types.ts` (outputs and optional params) - Wrapper added in `src/tools/` that: - Loads `.ps1` content and calls `runPowerShellScript` - Maps inputs to PowerShell param names and sets `JsonOutput: true` - Returns MCP `content` with text/markdown - Registration in `src/index.ts`: - Import wrapper, add tool entry to `ListTools`, add case in switch - Build and test: - `npm run build` - Start the server with your MCP client and call the tool --- ## Notes on legacy and exceptions - Some scripts (e.g., network diagnostics) currently output console text for direct use. When integrating with the server, ensure they support JSON output and that the wrapper passes `JsonOutput: true`. - File/module naming usually matches the PowerShell script (`hardware_monitor.ps1` ↔ `hardware_monitor.ts`). There are a few exceptions (e.g., `windows_registry.ps1` ↔ `registry.ts`). Consistency is preferred but not required—just import correctly in `src/index.ts`.

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jackalterman/windows-diagnostic-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server